package com.shanxin.boom.mall.web.controller.security;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.SerializationUtils;
import org.springframework.beans.factory.annotation.Value;

import com.andrew.session.DistributedSession;
import com.andrew.session.DistributedSessionAttribute;
import com.andrew.session.DistributedSessionDao;
import com.andrew.session.exception.DistributedSessionException;
import com.hwx.framework.base.util.RedisManager;

public class RedisSessionDao implements DistributedSessionDao {

    private RedisManager redisManager;

    private String sessionKeyPrefix = "shanxin_boom_mall_redis_session:";

    private String attributeSeperator = "|";

    @Value("${session.globalSessionTimeout}")
    private int GLOBAL_SESSION_TIMEOUT;

    @Override
    public DistributedSession getSession(String sessionId) {
        try {
            String value = redisManager.get(sessionKeyPrefix + sessionId, null);
            if (value == null) {
                return null;
            }
            DistributedSession session = (DistributedSession) SerializationUtils
                    .deserialize(Base64.decodeBase64(value));
            // 如果使用了dao管理attribute，此处需查询出
            if (session.isManageAttributeByDao()) {
                Map<String, DistributedSessionAttribute> distributedAttributes = new ConcurrentHashMap<String, DistributedSessionAttribute>();
                session.setDistributedAttributes(distributedAttributes);
                // 通过正则表达式获取该seessionId下的所有attributeKey
                String attributeKeyPre = sessionKeyPrefix + sessionId + attributeSeperator;
                Set<byte[]> keySet = redisManager.keys(attributeKeyPre + "*");
                for (byte[] keyByte : keySet) {
                    String attributeKey = new String(keyByte, "UTF-8");
                    String attributeName = attributeKey.substring(attributeKeyPre.length(), attributeKey.length());

                    DistributedSessionAttribute distributedAttributeValue = new DistributedSessionAttribute();

                    distributedAttributeValue.setIsValueChanged(false);
                    distributedAttributeValue.setAttributeValue((Serializable) getAttributeValue(attributeKey));
                    distributedAttributes.put(attributeName, distributedAttributeValue);
                }
            }
            return session;
        } catch (Exception e) {
            throw new DistributedSessionException("fail to get distributed session from nkv!", e);
        }
    }

    @Override
    public void createSession(DistributedSession distributedSession) {
        try {
            redisManager.set(sessionKeyPrefix + distributedSession.getId(),
                    Base64.encodeBase64String(SerializationUtils.serialize(distributedSession)),
                    GLOBAL_SESSION_TIMEOUT);
        } catch (Exception e) {
            throw new DistributedSessionException("fail to store new distributed session to nkv!", e);
        }
    }

    @Override
    public void updateSession(DistributedSession distributedSession) {
        try {
            redisManager.set(sessionKeyPrefix + distributedSession.getId(),
                    Base64.encodeBase64String(SerializationUtils.serialize(distributedSession)),
                    GLOBAL_SESSION_TIMEOUT);
            Map<String, DistributedSessionAttribute> distributedAttributes = distributedSession
                    .getDistributedAttributes();
            for (Map.Entry<String, DistributedSessionAttribute> entry : distributedAttributes.entrySet()) {
                String key = entry.getKey();
                DistributedSessionAttribute attribute = entry.getValue();

                if (attribute.getIsValueChanged()) {
                    redisManager.set(sessionKeyPrefix + distributedSession.getId() + attributeSeperator + key,
                            Base64.encodeBase64String(SerializationUtils.serialize(attribute.getAttributeValue())),
                            GLOBAL_SESSION_TIMEOUT);
                }
            }
        } catch (Exception e) {
            throw new DistributedSessionException("fail to update distributed session to nkv!", e);
        }
    }

    @Override
    public void removeSession(DistributedSession distributedSession) {
        try {
            redisManager.del(sessionKeyPrefix + distributedSession.getId());
            // 使用redis存储session attribute则同时删除对应的属性
            if (isManageSessionAttribute()) {
                Enumeration<String> names = distributedSession.getAttributeNames();
                while (names.hasMoreElements()) {
                    String name = names.nextElement();
                    redisManager.del(sessionKeyPrefix + distributedSession.getId() + attributeSeperator + name);
                }
            }
        } catch (Exception e) {
            throw new DistributedSessionException("fail to remove distributed session from nkv!", e);
        }
    }

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
    }

    @Override
    public boolean isManageSessionAttribute() {
        return true;
    }

    @Override
    public Object getAttributeValue(String sessionId, String attributeName) {
        try {
            String value = redisManager.get(sessionKeyPrefix + sessionId + attributeSeperator + attributeName, null);
            if (value == null) {
                return null;
            }
            return SerializationUtils.deserialize(Base64.decodeBase64(value));
        } catch (Exception e) {
            throw new DistributedSessionException("fail to get distributed session from nkv!", e);
        }
    }

    @Override
    public Object getAttributeValue(String attributeKey) {
        try {
            String value = redisManager.get(attributeKey, null);
            if (value == null) {
                return null;
            }
            return SerializationUtils.deserialize(Base64.decodeBase64(value));
        } catch (Exception e) {
            throw new DistributedSessionException("fail to get distributed session from nkv!", e);
        }
    }
}
